package com.tiancheng.trade.commom.web.http;

import cn.hutool.core.map.MapUtil;
import com.dtflys.forest.config.ForestConfiguration;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.http.ForestRequestType;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tiancheng.trade.commom.web.config.PlatformRequestConfig;
import com.tiancheng.trade.commom.core.data.Result;
import com.tiancheng.trade.commom.core.exception.PlatformServiceCallException;
import com.tiancheng.trade.commom.web.cache.AccessTokenCache;
import com.tiancheng.trade.commom.core.utils.JsonUtil;
import com.tiancheng.trade.commom.core.utils.PlatformCloudApiAuth;
import com.tiancheng.trade.commom.core.utils.SpringContextHolder;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.*;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.http.MediaType;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;


/**
 * @Author: likailun
 * @Description: 请求工具类
 * @Date: create in 2024/9/13 15:35
 */
@Getter
@Setter
public class PlatformRequest {
	private static final String tokenApi = "/auth/signIn/appAccessToken";
	private static final String ossTokenApi = "/auth/signIn/ossAppAccessToken";
	private static final Logger logger = LoggerFactory.getLogger(PlatformRequest.class);
	private Cache cache;
	private PlatformRequestConfig serviceConfig;

	private ForestConfiguration httpRequestUtils;
	private ObjectMapper objectMapper;


	/**
	 * @param requestConfig app服务配置信息
	 */
	public PlatformRequest(PlatformRequestConfig requestConfig) {
		this(new ConcurrentMapCache("AccessMap", false), requestConfig);
	}

	/**
	 * @param requestConfig app服务配置信息
	 * @param cache         缓存，用于存放token，避免多次计算token导致封号
	 */
	public PlatformRequest(Cache cache, PlatformRequestConfig requestConfig) {
		this.cache = cache;
		this.serviceConfig = requestConfig;
		this.httpRequestUtils = requestConfig.getConfiguration();
		this.objectMapper = SpringContextHolder.getBean(ObjectMapper.class);
	}

	private Header[] haderArrayAddHeaer(Header[] sourceHeadrs, Header addHager) {
		if (sourceHeadrs == null) {
			sourceHeadrs = new Header[1];
			sourceHeadrs[0] = addHager;
		} else {
			sourceHeadrs = ArrayUtils.add(sourceHeadrs, addHager);
		}
		return sourceHeadrs;
	}

	/**
	 * 设置trace信息
	 */
	private void initTrace() {

	}

	/**
	 * 发送添加认证的请求
	 *
	 * @param method      请求方式
	 * @param api         请求api
	 * @param urlParams   url参数
	 * @param bodyParam   body参数
	 * @param useFormData 使用form data传递body参数
	 * @param headers     请求头
	 */
	@SuppressWarnings("unchecked")
	private Result<?> request(String method, String api, Object urlParams, Object bodyParam, boolean useFormData, Header... headers) {
		this.initTrace();
		String path = api;
		if (!api.startsWith("http")) {
			path = this.serviceConfig.getServerPath() + api;
		}
		Map<String, Object> mapParam = null;
		if (urlParams != null) {
			if (Map.class.isAssignableFrom(urlParams.getClass())) {
				mapParam = (Map<String, Object>) urlParams;
			} else {
				mapParam = JsonUtil.fromJson(urlParams, new TypeReference<Map<String, Object>>() {
				});
			}
		}
		if (mapParam == null) {
			mapParam = new HashMap<>();
		}
		headers = this.haderArrayAddHeaer(headers, new BasicHeader(this.serviceConfig.getAccessTokenHeader(), this.getAccessToken()));
		if (this.serviceConfig.isEnableOssCompatible()) {
			headers = this.haderArrayAddHeaer(headers, new BasicHeader("access_token", this.getOssAccessToken()));
			mapParam.put("access_token", this.getOssAccessToken());
			mapParam.put("app_id", this.getServiceConfig().getClientId());
		}
		Map<String, String> apiAuthHeaders = PlatformCloudApiAuth.getAuthHeaders(this.serviceConfig.getApiClientId(), this.serviceConfig.getApiClientSecret());
		for (String headerName : apiAuthHeaders.keySet()) {
			headers = this.haderArrayAddHeaer(headers, new BasicHeader(headerName, apiAuthHeaders.get(headerName)));
		}
		mapParam.put("clientName", this.serviceConfig.getClientName());
		if (PlatformRequest.logger.isInfoEnabled()) {
			PlatformRequest.logger.info("本次请求地址: " + path);
			PlatformRequest.logger.info("本次请求url参数: " + mapParam);
			if (bodyParam != null && !(bodyParam instanceof HttpEntity)) {
				PlatformRequest.logger.info("本次请求body参数: " + JsonUtil.toJsonString(bodyParam));
			}
		}
		long start = System.currentTimeMillis();
		AtomicReference<Result<?>> result = new AtomicReference<>();
		// 20002 token无效或强制失效时, 重新获取token, 并重试10次
		for (int i = 0; i < 10; i++) {
			ForestRequest<?> request = httpRequestUtils.request();
			request.url(path);
			request.setType(getType(method));
			setHeader(request, headers);
			setParams(request, mapParam, bodyParam, useFormData);
			AtomicReference<Result<?>> finalResult = result;
			request.onSuccess((data, forestRequest, forestResponse) -> {
				finalResult.set(JsonUtil.fromJson(JsonUtil.toJsonString(data), Result.class));
			});
			request.onError((ex, forestRequest, forestResponse) -> {
				throw new PlatformServiceCallException("请求异常:" + ex.getMessage());
			});
			request.execute();
			if (result.get().resultIsError()) {
				if (result.get().getErrorCode() == 20002 && i != 9) {
					AccessTokenCache.delAccessToken(serviceConfig.getClientId());
					// 重置签名
					for (int h = 0; h < headers.length; h++) {
						if (headers[h].getName().equals(this.getServiceConfig().getAccessTokenHeader())) {
							headers[h] = new BasicHeader(this.serviceConfig.getAccessTokenHeader(), this.getAccessToken());
							break;
						}
					}
				} else {
					throw new PlatformServiceCallException(result.get().getMsg());
				}
			} else {
				break;
			}
		}
		if (PlatformRequest.logger.isInfoEnabled()) {
			PlatformRequest.logger.info("返回结果: " + JsonUtil.toJsonString(result));
			long end = System.currentTimeMillis();
			PlatformRequest.logger.info("请求耗时: {}毫秒", (end - start));
		}
		return result.get();
	}

	private void setParams(ForestRequest<?> request, Map<String, Object> urlParams, Object bodyParam, boolean useFormData) {
		if (MapUtil.isNotEmpty(urlParams)) {
			request.addQuery(urlParams);
		}
		if (Objects.nonNull(bodyParam)) {
			if (useFormData) {
				request.addQuery(bodyParam);
			} else {
				request.addBody(bodyParam);
			}
		}
	}

	/**
	 * 设置请求头
	 *
	 * @param request
	 * @param headers
	 */
	private void setHeader(ForestRequest<?> request, Header... headers) {
		if (null != headers) {
			for (Header header : headers) {
				request.addHeader(header.getName(), header.getValue());
			}
		}
	}

	private ForestRequestType getType(String method) {
		return switch (method.toUpperCase()) {
			case HttpGet.METHOD_NAME -> ForestRequestType.GET;
			case HttpPut.METHOD_NAME -> ForestRequestType.PUT;
			case HttpPatch.METHOD_NAME -> ForestRequestType.PATCH;
			default -> ForestRequestType.POST;
		};
	}

	/**
	 * 发送添加认证的请求
	 *
	 * @param method    请求方式
	 * @param api       请求api
	 * @param urlParams url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	private Result<?> request(String method, String api, Object urlParams, Object bodyParam, Header... headers) {
		return this.request(method, api, urlParams, bodyParam, false, headers);
	}

	/**
	 * get请求
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> getRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpGet.METHOD_NAME, api, urlParam, bodyParam, headers);
	}

	/**
	 * get请求
	 *
	 * @param api      请求api
	 * @param urlParam url参数
	 * @param headers  请求头
	 */
	public Result<?> getRequest(String api, Object urlParam, Header... headers) {
		return this.getRequest(api, urlParam, null, headers);
	}

	/**
	 * get请求
	 *
	 * @param api     请求api
	 * @param headers 请求头
	 */
	public Result<?> getRequest(String api, Header... headers) {
		return this.getRequest(api, null, headers);
	}

	/**
	 * delete请求
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> deleteRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpDelete.METHOD_NAME, api, urlParam, bodyParam, headers);
	}

	/**
	 * delete请求
	 *
	 * @param api      请求api
	 * @param urlParam url参数
	 * @param headers  请求头
	 */
	public Result<?> deleteRequest(String api, Object urlParam, Header... headers) {
		return this.deleteRequest(api, urlParam, null, headers);
	}

	/**
	 * delete请求
	 *
	 * @param api     请求api
	 * @param headers 请求头
	 */
	public Result<?> deleteRequest(String api, Header... headers) {
		return this.deleteRequest(api, null, headers);
	}


	/**
	 * post请求, x-wwww-form-urlencoded传递参数
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> formUrlencodedPostRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpPost.METHOD_NAME, api, urlParam, bodyParam, true, headers);
	}

	/**
	 * post请求, x-wwww-form-urlencoded传递参数
	 *
	 * @param api       请求api
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> formUrlencodedPostRequest(String api, Object bodyParam, Header... headers) {
		return this.request(HttpPost.METHOD_NAME, api, null, bodyParam, true, headers);
	}

	/**
	 * put请求, x-wwww-form-urlencoded传递参数
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> formUrlencodedPutRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpPut.METHOD_NAME, api, urlParam, bodyParam, true, headers);
	}

	/**
	 * put请求, x-wwww-form-urlencoded传递参数
	 *
	 * @param api       请求api
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> formUrlencodedPutRequest(String api, Object bodyParam, Header... headers) {
		return this.request(HttpPut.METHOD_NAME, api, null, bodyParam, true, headers);
	}

	/**
	 * patch请求, x-wwww-form-urlencoded传递参数
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> formUrlencodedPatchRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpPatch.METHOD_NAME, api, urlParam, bodyParam, true, headers);
	}

	/**
	 * patch请求, x-wwww-form-urlencoded传递参数
	 *
	 * @param api       请求api
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> formUrlencodedPatchRequest(String api, Object bodyParam, Header... headers) {
		return this.request(HttpPatch.METHOD_NAME, api, null, bodyParam, true, headers);
	}

	/**
	 * post请求, application/json传递参数
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> jsonPostRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpPost.METHOD_NAME, api, urlParam, bodyParam, headers);
	}

	/**
	 * post请求, application/json传递参数
	 *
	 * @param api       请求api
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> jsonPostRequest(String api, Object bodyParam, Header... headers) {
		return this.jsonPostRequest(api, null, bodyParam, headers);
	}

	/**
	 * patch请求, application/json传递参数
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> jsonPatchRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpPatch.METHOD_NAME, api, urlParam, bodyParam, headers);
	}

	/**
	 * patch请求, application/json传递参数
	 *
	 * @param api       请求api
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> jsonPatchRequest(String api, Object bodyParam, Header... headers) {
		return this.jsonPatchRequest(api, null, bodyParam, headers);
	}

	/**
	 * put请求, application/json传递参数
	 *
	 * @param api       请求api
	 * @param urlParam  url参数
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> jsonPutRequest(String api, Object urlParam, Object bodyParam, Header... headers) {
		return this.request(HttpPut.METHOD_NAME, api, urlParam, bodyParam, headers);
	}

	/**
	 * put请求, application/json传递参数
	 *
	 * @param api       请求api
	 * @param bodyParam body参数
	 * @param headers   请求头
	 */
	public Result<?> jsonPutRequest(String api, Object bodyParam, Header... headers) {
		return this.jsonPutRequest(api, null, bodyParam, headers);
	}

	/**
	 * 获取认证public key
	 */
	public String getAuthPublicKey() {
		return this.getRequest(this.serviceConfig.getAuthServiceApiPrefix() + "/auth/token/getAuthPublicKey").getData().toString();
	}


	public String extendRequest(String path,String method,Map<String,Object> mapParam,Object bodyParam,boolean useFormData, Header... headers){
		ForestRequest<?> request = httpRequestUtils.request();
		request.url(path);
		request.setType(getType(method));
		setHeader(request, headers);
		setParams(request, mapParam, bodyParam, useFormData);
		AtomicReference<String> result=null;
		request.onSuccess((data, forestRequest, forestResponse) -> {
			result.set(JsonUtil.toJsonString(data));
		});
		request.onError((ex, forestRequest, forestResponse) -> {
			throw new PlatformServiceCallException("请求异常:" + ex.getMessage());
		});
		request.execute();
		return result.get();
	}

	/**
	 * 获取accessToken
	 */
	public String getAccessToken() {
		String redisAccesToken = null;
		Optional<String> accessToken = AccessTokenCache.getAccessToken(serviceConfig.getClientId());
		if (accessToken.isPresent()) {
			return accessToken.get();
		}
		// 如果缓存里面没有则请求相关接口生成
		synchronized (this) {
			Optional<String> accessToken1 = AccessTokenCache.getAccessToken(serviceConfig.getClientId());
			if (accessToken1.isPresent()) {
				return accessToken1.get();
			}
			Map<String, Object> requestMap = new HashMap<>();
			long timestamp = System.currentTimeMillis();
			String rand = UUID.randomUUID().toString();
			String clientId = this.serviceConfig.getClientId();
			String clientSecret = this.serviceConfig.getClientSecret();
			String sigin = this.signature(timestamp + "", rand, clientId, clientSecret);
			requestMap.put("timestamp", timestamp);
			requestMap.put("rand", rand);
			requestMap.put("clientId", clientId);
			requestMap.put("signature", sigin);
			String appPath = this.serviceConfig.getServerPath();
			String url = appPath + this.serviceConfig.getAuthServiceApiPrefix() + PlatformRequest.tokenApi;
			Result appDataResultDTO = this.httpRequestUtils.post(url).addBody(requestMap, MediaType.APPLICATION_JSON_VALUE).execute(Result.class);
			if (appDataResultDTO.resultIsError()) {
				throw new PlatformServiceCallException(appDataResultDTO.getMsg());
			} else {
				String token = (String) appDataResultDTO.dataToMap().get("accessToken");
				long expires = (int) appDataResultDTO.dataToMap().get("expiresIn");
				AccessTokenCache.setAccessToken(serviceConfig.getClientId(), token, expires - 300);
				return token;
			}
		}
	}

	/**
	 * 获取oss accessToken
	 */
	public String getOssAccessToken() {
		String redisAccesToken = null;
		Optional<String> ossAccessToken = AccessTokenCache.getOssAccessToken(serviceConfig.getClientId());
		if (ossAccessToken.isPresent()) {
			return ossAccessToken.get();
		}
		// 如果缓存里面没有则请求相关接口生成
		synchronized (this) {
			Optional<String> ossAccessToken1 = AccessTokenCache.getOssAccessToken(serviceConfig.getClientId());
			if (ossAccessToken1.isPresent()) {
				return ossAccessToken1.get();
			}
			Map<String, Object> requestMap = new HashMap<>();
			String rand = UUID.randomUUID().toString();
			String clientId = this.serviceConfig.getClientId();
			String clientSecret = this.serviceConfig.getClientSecret();
			requestMap.put("app_id", clientId);
			requestMap.put("rand", rand);
			requestMap.put("signature", this.signature(rand, clientId, clientSecret));
			String appPath = this.serviceConfig.getServerPath();
			String url = appPath + this.serviceConfig.getAuthServiceApiPrefix() + PlatformRequest.ossTokenApi;
			Result appDataResultDTO = this.httpRequestUtils.post(url).addBody(requestMap, MediaType.APPLICATION_JSON_VALUE).execute(Result.class);
			if (appDataResultDTO.resultIsError()) {
				throw new PlatformServiceCallException(appDataResultDTO.getMsg());
			} else {
				String token = (String) appDataResultDTO.dataToMap().get("access_token");
				long expires = (int) appDataResultDTO.dataToMap().get("expires_in");
				AccessTokenCache.setOssAccessToken(serviceConfig.getClientId(), token, expires - 300);
				return token;
			}
		}
	}

	/**
	 * 签名
	 */
	public String signature(String... param) {
		Set<String> signatureVar = new TreeSet<>(Arrays.asList(param));
		StringBuilder signatureStr = new StringBuilder();
		for (String key : signatureVar) {
			signatureStr.append(key);
		}
		String sigin = DigestUtils.sha1Hex(signatureStr.toString());
		try {
			sigin = URLEncoder.encode(sigin, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
		return sigin;
	}
}
